
package com.evancharlton.mileage.tasks;

import android.content.ContentResolver;
import android.database.Cursor;
import android.util.Log;

import com.evancharlton.mileage.VehicleStatisticsActivity;
import com.evancharlton.mileage.dao.CachedValue;
import com.evancharlton.mileage.dao.Fillup;
import com.evancharlton.mileage.dao.FillupSeries;
import com.evancharlton.mileage.dao.Vehicle;
import com.evancharlton.mileage.exceptions.InvalidFieldException;
import com.evancharlton.mileage.math.Calculator;
import com.evancharlton.mileage.provider.Statistics;
import com.evancharlton.mileage.provider.tables.CacheTable;
import com.evancharlton.mileage.provider.tables.FillupsTable;

public class VehicleStatisticsTask extends
        AttachableAsyncTask<VehicleStatisticsActivity, Cursor, Integer, Integer> {
    private static final String TAG = "VehicleStatisticsTask";

    private ContentResolver mContentResolver;
    private int mProgress = 0;
    private int mTotal = 0;

    @Override
    public void attach(VehicleStatisticsActivity activity) {
        super.attach(activity);
        mContentResolver = activity.getContentResolver();
    }

    @Override
    protected void onPreExecute() {
        Log.d(TAG, "Calculation starting...");
        getParent().startCalculations();
    }

    @Override
    protected Integer doInBackground(Cursor... cursors) {
        // delete the cache
        String[] args = new String[] {
                String.valueOf(getParent().getVehicle().getId())
        };
        mContentResolver.delete(CacheTable.BASE_URI, CachedValue.ITEM + " = ?", args);

        String selection = Fillup.VEHICLE_ID + " = ?";
        args = new String[] {
                String.valueOf(getParent().getVehicle().getId())
        };

        Cursor cursor = mContentResolver.query(FillupsTable.BASE_URI, FillupsTable.PROJECTION,
                selection, args, Fillup.ODOMETER + " asc");
        mTotal = cursor.getCount();

        if (mTotal <= 1) {
            Log.d(TAG, "Not enough fillups to calculate statistics");
            return 0;
        }

        Log.d("CalculateTask", "Recalculating...");
        // recalculate a whole bunch of shit
        FillupSeries series = new FillupSeries();
        // math gets understandably funky around Double.(MAX|MIN)_VALUE
        final double MAX = 10000D;
        final double MIN = -10000D;

        double totalVolume = 0D;
        double firstVolume = -1D;
        double minVolume = MAX;
        double maxVolume = MIN;

        double minDistance = MAX;
        double maxDistance = MIN;

        double totalCost = 0D;
        double minCost = MAX;
        double maxCost = MIN;

        double minEconomy = MAX;
        double maxEconomy = MIN;

        double minCostPerDistance = MAX;
        double maxCostPerDistance = MIN;

        double minPrice = MAX;
        double maxPrice = MIN;

        double minLatitude = MAX;
        double maxLatitude = MIN;
        double minLongitude = MAX;
        double maxLongitude = MIN;

        double lastMonthCost = 0D;
        double lastYearCost = 0D;

        final long lastYear = System.currentTimeMillis() - Calculator.YEAR_MS;
        final long lastMonth = System.currentTimeMillis() - Calculator.MONTH_MS;

        final Vehicle vehicle = getParent().getVehicle();

        switch (vehicle.getEconomyUnits()) {
            case Calculator.GALLONS_PER_100KM:
            case Calculator.LITRES_PER_100KM:
            case Calculator.IMP_GAL_PER_100KM:
                // Have to flip over the best/worst
                minEconomy = MIN;
                maxEconomy = MAX;
                break;
        }

        while (cursor.moveToNext()) {
            if (isCancelled()) {
                getParent().stopCalculations();
                break;
            }

            Fillup fillup = new Fillup(cursor);
            series.add(fillup);

            if (fillup.hasPrevious()) {
                double distance = fillup.getDistance();
                if (distance > maxDistance) {
                    maxDistance = distance;
                    update(Statistics.MAX_DISTANCE, maxDistance);
                } else {
                    publishProgress();
                }

                if (distance < minDistance) {
                    minDistance = distance;
                    update(Statistics.MIN_DISTANCE, minDistance);
                } else {
                    publishProgress();
                }

                double economy = Calculator.fillupEconomy(vehicle, series);
                if (economy > 0D) {
                    if (Calculator.isBetterEconomy(vehicle, economy, maxEconomy)) {
                        maxEconomy = economy;
                        update(Statistics.MAX_ECONOMY, maxEconomy);
                    } else {
                        publishProgress();
                    }

                    if (!Calculator.isBetterEconomy(vehicle, economy, minEconomy)) {
                        minEconomy = economy;
                        update(Statistics.MIN_ECONOMY, minEconomy);
                    } else {
                        publishProgress();
                    }
                }

                double costPerDistance = fillup.getCostPerDistance();
                if (costPerDistance > maxCostPerDistance) {
                    maxCostPerDistance = costPerDistance;
                    update(Statistics.MAX_COST_PER_DISTANCE, maxCostPerDistance);
                } else {
                    publishProgress();
                }

                if (costPerDistance < minCostPerDistance) {
                    minCostPerDistance = costPerDistance;
                    update(Statistics.MIN_COST_PER_DISTANCE, minCostPerDistance);
                } else {
                    publishProgress();
                }

                long timestamp = fillup.getTimestamp();
                if (timestamp >= lastMonth) {
                    lastMonthCost += fillup.getTotalCost();
                    update(Statistics.LAST_MONTH_COST, lastMonthCost);
                } else {
                    publishProgress();
                }

                if (timestamp >= lastYear) {
                    lastYearCost += fillup.getTotalCost();
                    update(Statistics.LAST_YEAR_COST, lastYearCost);
                } else {
                    publishProgress();
                }

            } else {
                publishProgress(8);
            }

            double volume = fillup.getVolume();
            if (firstVolume == -1D) {
                firstVolume = volume;
            } else {
                publishProgress();
            }

            if (volume > maxVolume) {
                maxVolume = volume;
                update(Statistics.MAX_FUEL, maxVolume);
            } else {
                publishProgress();
            }

            if (volume < minVolume) {
                minVolume = volume;
                update(Statistics.MIN_FUEL, minVolume);
            } else {
                publishProgress();
            }

            totalVolume += volume;
            update(Statistics.TOTAL_FUEL, totalVolume);

            double cost = fillup.getTotalCost();
            if (cost > maxCost) {
                maxCost = cost;
                update(Statistics.MAX_COST, maxCost);
            } else {
                publishProgress();
            }

            if (cost < minCost) {
                minCost = cost;
                update(Statistics.MIN_COST, minCost);
            } else {
                publishProgress();
            }

            totalCost += cost;
            update(Statistics.TOTAL_COST, totalCost);

            double price = fillup.getUnitPrice();
            if (price > maxPrice) {
                maxPrice = price;
                update(Statistics.MAX_PRICE, maxPrice);
            } else {
                publishProgress();
            }

            if (price < minPrice) {
                minPrice = price;
                update(Statistics.MIN_PRICE, minPrice);
            } else {
                publishProgress();
            }

            double latitude = fillup.getLatitude();
            if (latitude > maxLatitude && latitude != 0) {
                maxLatitude = latitude;
                update(Statistics.NORTH, maxLatitude);
            } else {
                publishProgress();
            }

            if (latitude < minLatitude && latitude != 0) {
                minLatitude = latitude;
                update(Statistics.SOUTH, minLatitude);
            } else {
                publishProgress();
            }

            double longitude = fillup.getLongitude();
            if (longitude > maxLongitude && longitude != 0) {
                maxLongitude = longitude;
                update(Statistics.EAST, maxLongitude);
            } else {
                publishProgress();
            }

            if (longitude < minLongitude && longitude != 0) {
                minLongitude = longitude;
                update(Statistics.WEST, minLongitude);
            } else {
                publishProgress();
            }

            double avgFuel = totalVolume / series.size();
            update(Statistics.AVG_FUEL, avgFuel);

            double avgEconomy = Calculator.averageEconomy(vehicle, series);
            update(Statistics.AVG_ECONOMY, avgEconomy);

            double avgDistance = Calculator.averageDistanceBetweenFillups(series);
            update(Statistics.AVG_DISTANCE, avgDistance);

            double avgCost = Calculator.averageFillupCost(series);
            update(Statistics.AVG_COST, avgCost);

            double avgCostPerDistance = Calculator.averageCostPerDistance(series);
            update(Statistics.AVG_COST_PER_DISTANCE, avgCostPerDistance);

            double avgPrice = Calculator.averagePrice(series);
            update(Statistics.AVG_PRICE, avgPrice);

            double fuelPerDay = Calculator.averageFuelPerDay(series);
            update(Statistics.FUEL_PER_YEAR, fuelPerDay * 365);

            double costPerDay = Calculator.averageCostPerDay(series);
            update(Statistics.AVG_MONTHLY_COST, costPerDay * 30);
            update(Statistics.AVG_YEARLY_COST, costPerDay * 365);

            try {
                fillup.saveIfChanged(getParent());
            } catch (InvalidFieldException e) {
                Log.e(TAG, "Couldn't save in-memory changes.", e);
            }
        }

        // Force an update for conditional statistics to ensure they happen
        update(Statistics.MAX_DISTANCE, maxDistance);
        update(Statistics.MIN_DISTANCE, minDistance);
        update(Statistics.MAX_ECONOMY, maxEconomy);
        update(Statistics.MIN_ECONOMY, minEconomy);
        update(Statistics.MAX_COST_PER_DISTANCE, maxCostPerDistance);
        update(Statistics.MIN_COST_PER_DISTANCE, minCostPerDistance);
        update(Statistics.LAST_MONTH_COST, lastMonthCost);
        update(Statistics.LAST_YEAR_COST, lastYearCost);
        update(Statistics.NORTH, maxLatitude);
        update(Statistics.SOUTH, minLatitude);
        update(Statistics.EAST, maxLongitude);
        update(Statistics.WEST, minLongitude);

        cursor.close();

        return 0;
    }

    private void update(Statistics.Statistic statistic, double value) {
        statistic.setValue(value);
        getParent().getAdapter().setValue(statistic, value);
        publishProgress();
    }

    @Override
    protected void onProgressUpdate(Integer... updates) {
        if (mTotal > 0) {
            getParent().getProgressBar().setIndeterminate(false);
            getParent().getProgressBar().setMax(mTotal * Statistics.STATISTICS.size());
            mTotal = 0;
        }
        if (updates.length > 0) {
            mProgress += updates[0];
        } else {
            mProgress += 1;
        }
        getParent().getProgressBar().setProgress(mProgress);
        getParent().getAdapter().notifyDataSetChanged();
    }

    @Override
    protected void onPostExecute(Integer done) {
        if (getParent().getAdapter() == null) {
            getParent().setAdapter(getParent().getCacheCursor());
        } else {
            getParent().getAdapter().notifyDataSetChanged();
        }
        getParent().stopCalculations();

        Log.d(TAG, "Done recalculating!");

        getParent().getAdapter().flush();
    }
}
